package org.piwicode.bywordofmouth.net;

import com.google.common.base.Preconditions;
import com.google.common.io.CountingInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author pierre
 */
public class Client {

    private static final Logger LOG = Logger.getLogger(Client.class.getName());
    private final Socket socket;
    private final ObjectOutputStream oos;
    private final ObjectInputStream ois;
    final CountingInputStream cis;

    Client(Socket socket) throws IOException {
        this.socket = Preconditions.checkNotNull(socket);
        this.oos = new ObjectOutputStream(socket.getOutputStream());
        this.cis = new CountingInputStream(socket.getInputStream());
        this.ois = new ObjectInputStream(cis);
    }

    public static Client connect(final String host, int port) throws UnknownHostException, IOException {
        LOG.log(Level.INFO, "Connecting {0}:{1}", new Object[]{host, port});
        return new Client(new Socket(host, port));
    }

    public void shutdown() throws IOException {
        LOG.log(Level.INFO, "Shutdown client socket after reading {0}", cis.getCount());
        try {
            try {
                socket.close();
            } finally {
                oos.close();
            }
        } finally {
            ois.close();
        }
    }

    public <T> T wrap(Class<T> clazz) {
        return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{clazz},
                new InvocationHandlerImpl());
    }

    private class InvocationHandlerImpl implements InvocationHandler {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            oos.writeObject(new ServiceRequest(method.getName(), args));
            oos.reset();
            oos.flush();
            return ois.readObject();
        }
    }
}
